package cn.net.mugui.net.p2p;

import java.io.IOException;
import java.net.DatagramSocket;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Map.Entry;

import com.mugui.base.base.Autowired;
import com.mugui.base.base.Component;
import com.mugui.base.client.net.bagsend.NetCall;
import com.mugui.base.client.net.bean.NetBag;

import cn.hutool.cache.impl.TimedCache;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.digest.MD5;
import cn.net.mugui.net.key.utils.Base58;
import cn.net.mugui.net.udp.UdpBag;
import cn.net.mugui.net.udp.UdpBaseServer;
import cn.net.mugui.net.udp.UdpServer;
import cn.net.mugui.net.udp.UdpServer.ReceiveListener;
import cn.net.mugui.net.udp.UdpServerManager;

/**
 * 网络服务处理器
 *
 * @author 木鬼
 */
@Component
public class Server {

	ReceiveListener receiveListene = new ReceiveListener() {

		@Override
		public boolean sucess(DatagramSocket socket, UdpServer server, UdpBag resolve) {

			return false;
		}

		@Override
		public boolean retPing(DatagramSocket socket, UdpServer server, UdpBag resolve) {
			if (server != null)
				synchronized (server) {
					System.out.println("retPing" + server.hashCode() + " " + Base58.encode(resolve.getFrom()) + " "
							+ Base58.encode(resolve.getTo()));
					server.notifyAll();
				}
			return false;
		}

		@Override
		public boolean ping(DatagramSocket socket, UdpServer server, UdpBag resolve) {
			return false;
		}
	};
	@Autowired
	private UdpServerManager udpServerManager;
	@Autowired
	private ServerManager serverManager;

	/**
	 * 基本服务
	 */
	@Autowired
	UdpBaseServer base_server;

	/**
	 * ping 这个服务器,并等待反馈
	 *
	 * @param serverAttr
	 * @author 木鬼
	 */
	public boolean pingWaitRet(ServerAttr serverAttr, long waittime) {
		UdpServer udpServer = udpServerManager.get(serverAttr.getAddress(), serverAttr.getHost(), serverAttr.getPort());
		try {
			udpServer.setReceiveListener(receiveListene);
			long time = System.currentTimeMillis();
			while (System.currentTimeMillis() - time < waittime) {
				ping(udpServer, serverAttr);
				synchronized (udpServer) {
					try {
						long time2 = System.currentTimeMillis();
						udpServer.wait(1000);
						if (System.currentTimeMillis() - time2 < 1000) {
							break;
						}
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
			if (System.currentTimeMillis() - time >= waittime) {
				return false;
			}
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}
		serverAttr.setHost(udpServer.getTo_host()).setPort(udpServer.getTo_port());
		return true;
	}

	/**
	 * 发送一个ping包
	 *
	 * @param serverAttr
	 * @param udpServer
	 * @throws IOException
	 * @author 木鬼
	 */
	public void ping(UdpServer udpServer, ServerAttr serverAttr) throws IOException {
		UdpBag bag = new UdpBag();
		bag.setType(UdpBag.type_0);
		bag.setFrom(Base58.decode(ServerAttr.self.getAddress()));
		bag.setTo(Base58.decode(serverAttr.getAddress()));
		bag.setBag_hash(create.digest(System.currentTimeMillis() + ""));
//		System.out.println(
//				"ping->" + udpServer.hashCode() + " " + ServerAttr.self.getAddress() + " " + serverAttr.getAddress());

		udpServer.send(base_server.base(), bag, 0);
	}

	/**
	 * 向某个地址发送消息
	 *
	 * @param msg
	 * @param to_address
	 * @author 木鬼
	 */
	public void send(String msg, String to_address) {
		ServerAttr serverAttr = serverManager.get(to_address);
		UdpServer value = udpServerManager.get(to_address);
		send(base_server.base(), value, serverAttr, msg);
	}

	/**
	 * 向某个地址发送消息
	 *
	 * @param msg
	 * @param to_address
	 * @author 木鬼
	 */
	public NetCall send(NetBag msg, String to_address) {
		ServerAttr serverAttr = serverManager.get(to_address);
		UdpServer value = udpServerManager.get(to_address);
		if (value == null) {
			return null;
		}
		msg.setHash(
				create.digestHex(msg.getData() + msg.getFunc() + System.currentTimeMillis() + RandomUtil.randomInt()));

		send(base_server.base(), value, serverAttr, msg.toString());
		NetCall netCall = new NetCall();
		netCallP2PManager.add(msg.getHash(), netCall);
		return netCall;
	}

	/**
	 * 向某个地址发送消息
	 *
	 * @param msg
	 * @param to_address
	 * @author 木鬼
	 */
	public NetCall send(String func, Object object, String to_address) {
		return send(new NetBag().setFunc(func).setData(object), to_address);
	}

	/**
	 * 向某个地址的测试端口发送消息
	 * 
	 * @param func
	 * @param object
	 * @param to_address
	 * @return
	 */
	public NetCall sendTest(String func, Object object, String to_address) {
		return sendTest(new NetBag().setFunc(func).setData(object), to_address);
	}

	private NetCall sendTest(NetBag msg, String to_address) {
		ServerAttr serverAttr = serverManager.get(to_address);
		UdpServer value = udpServerManager.get(to_address);
		msg.setHash(
				create.digestHex(msg.getData() + msg.getFunc() + System.currentTimeMillis() + RandomUtil.randomInt()));
		sendTest(base_server.base(), value, serverAttr, msg.toString());
		NetCall netCall = new NetCall();
		netCallP2PManager.add(msg.getHash(), netCall);
		return netCall;
	}

	private void sendTest(DatagramSocket base, UdpServer value, ServerAttr serverAttr, String string) {
		if (serverAttr.getCheck_port() != null) {
			value.setCheck_port(serverAttr.getCheck_port());
			send(base, value, serverAttr, string.getBytes(), (byte) 0, 1);
		}
	}

	/**
	 * 
	 * @param base
	 * @param value
	 * @param serverAttr
	 * @param msg
	 */
	public void send(DatagramSocket base, UdpServer value, ServerAttr serverAttr, String msg) {
		send(base, value, serverAttr, msg.getBytes(), (byte) 0);
	}

	/**
	 * 向所有人广播消息
	 *
	 * @param msg
	 * @author 木鬼
	 */
	public void broadcast(String msg) {
		broadcast(msg.getBytes(StandardCharsets.UTF_8));
	}

	/**
	 * 广播
	 *
	 * @param msg
	 * @author 木鬼
	 */
	public void broadcast(byte[] msg) {
		Iterator<Entry<String, ServerAttr>> iterator = serverManager.entrySet().iterator();
		int i = 0;
		while (iterator.hasNext() && i < 5) {
			Entry<String, ServerAttr> next = iterator.next();
			ServerAttr value = next.getValue();
			if (value.getStatus() == ServerAttr.status_3) {
				UdpServer udpServer = udpServerManager.get(value.getAddress(), value.getHost(), value.getPort());
				send(udpServer, value, msg, (byte) 1);
				i++;
			}
		}
	}

	public void broadcast(UdpBag resolve) {
		Iterator<Entry<String, ServerAttr>> iterator = serverManager.entrySet().iterator();
		int i = 0;
		String from = Base58.encode(resolve.getFrom());
		String to = Base58.encode(resolve.getTo());

		while (iterator.hasNext() && i < 15) {
			Entry<String, ServerAttr> next = iterator.next();
			ServerAttr value = next.getValue();
			if (value.getStatus() == ServerAttr.status_3) {
				if (value.getAddress().equals(from) || value.getAddress().equals(to)) {
					continue;
				}
				UdpServer udpServer = udpServerManager.get(value.getAddress(), value.getHost(), value.getPort());
				resolve.setTo(Base58.decode(value.getAddress()));
				send(udpServer, resolve);
				i++;
			}
		}
	}

	private void send(UdpServer udpServer, UdpBag resolve) {
		udpServer.send(base_server.base(), resolve, 0);
	}

	private void send(UdpServer udpServer, ServerAttr value, byte[] msg, byte i) {
		send(base_server.base(), udpServer, value, msg, i);
	}

	MD5 create = MD5.create();

	public void send(UdpServer udpServer, ServerAttr serverAttr, byte[] msg) {
		send(udpServer, serverAttr, msg, (byte) 0);

	}

	@Autowired
	private NetCallP2PManager netCallP2PManager;

	TimedCache<String, NetCall> newTimedCache = new TimedCache<String, NetCall>(60000);
	{
		newTimedCache.schedulePrune(60000);
	}

	public void send(DatagramSocket base, UdpServer udpServer, ServerAttr serverAttr, byte[] msg, byte i) {
		send(base, udpServer, serverAttr, msg, (byte) i, 0);
	}

	/**
	 * 
	 * @param base
	 * @param udpServer
	 * @param serverAttr
	 * @param msg
	 * @param type       0主端口 1测试端口
	 * @param bag_type   0 一般消息 1 广播类型
	 */
	public void send(DatagramSocket base, UdpServer udpServer, ServerAttr serverAttr, byte[] msg, byte bag_type,
			int type) {
		if (serverAttr == null)
			return;
		UdpBag bag = new UdpBag();
		bag.setType(UdpBag.type_5);
		bag.setFrom(Base58.decode(ServerAttr.self.getAddress()));
		bag.setTo(Base58.decode(serverAttr.getAddress()));
		bag.setData(msg);
		bag.setData_hash(create.digest(msg));
		bag.setData_index((byte) 1);
		bag.setBag_hash(create.digest(bag.getData_hash()));
		bag.setBag_type(bag_type);
		udpServer.send(base, bag, type);
	}

}
